package common.dal;

import java.sql.*;
import java.util.Properties;

import common.*;
import common.logger.CompositeLogger;
import common.logger.Logger.LogLevel;

/**
 * A single transaction, to be send to Databases to facilitate transactional actions.<br>
 * Each transaction must be committed using {@link Transaction#commit()} after a change set is done.
 * 
 * @author Gal Lalouche
 */
public class Transaction implements AutoCloseable {
	private static final String			CALLING_METHOD_HELPER	= "<init>";
	private static final ConnectionPool	pool;
	/**
	 * The method that set the transaction (used for debugging)
	 */
	private final String				callingMethod;
	private boolean						hasCommit				= false;
	
	static {
		try {
			// sets the pool
			Properties props = Configuration.getInstance().getProps();
			int maxConnections = Integer.parseInt(props.getProperty("maxDbConnections"));
			pool = new ConnectionPool(maxConnections, props);
		} catch (SQLException e) {
			e.printStackTrace();
			throw new AssertionError(e);
		}
	}
	
	// private static Collection<Transaction> uncommitted = new ArrayList<>();
	private final Connection			connection;
	
	/**
	 * Creates a new transaction. It is a blocking call, meaning if there are no available connection, it will block
	 * until they become available.
	 */
	public Transaction() {
		connection = pool.getConnection();
		StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
		if (stackTrace[2].getMethodName().equals(CALLING_METHOD_HELPER)) {
			callingMethod = stackTrace[3].getMethodName();
		} else {
			callingMethod = stackTrace[2].getMethodName();
			
		}
		CompositeLogger.getInstance().log(LogLevel.DEBUG, "new transaction from: " + callingMethod);
	}
	
	/**
	 * @return the connection
	 */
	public Connection getConnection() {
		return connection;
	}
	
	/**
	 * Commits all changes done on the transaction, and resets the transaction on the DBs used.
	 * 
	 * @throws SQLException If the commit failed for some reason (transaction will be rolledback).
	 */
	public void commit() throws SQLException {
		if (hasCommit) {
			CompositeLogger.getInstance().log(LogLevel.ASSERT, "recommit attempted on " + callingMethod);
			throw new IllegalStateException("transaction " + callingMethod + " has already been committed!");
		}
		CompositeLogger.getInstance().log(LogLevel.DEBUG, "commiting transaction from: " + callingMethod);
		
		boolean result = pool.commit(connection);
		if (result == false) {
			throw new SQLException("commit failed");
		}
		hasCommit = true;
	}
	
	private void rollBack() {
		if (hasCommit) {
			CompositeLogger.getInstance().log(LogLevel.ASSERT, "reolling attempted on " + callingMethod);
			throw new AssertionError("transaction " + callingMethod + " has already been committed!");
		}
		CompositeLogger.getInstance().log(LogLevel.DEBUG, "rolling back transaction from: " + callingMethod);
		
		pool.rollBack(connection);
		hasCommit = true;
	}
	
	// public static void commitAll() {
	// for (Transaction t : uncommitted) {
	// try {
	// t.connection.commit();
	// } catch (SQLException e) {
	//
	// e.printStackTrace();
	// throw new AssertionError(e);
	// }
	// ConsoleLogger.getInstance().log(LogLevel.ERROR, "1.5");
	// }
	// }
	
	/**
	 * @return The method that set the transaction.
	 */
	public String getCallingMethod() {
		return callingMethod;
	}
	
	/**
	 * Closes the transaction - if {@link #commit()} has been called, nothing will happen, otherwise all changes will be
	 * rolled back
	 */
	@Override
	public void close() {
		if (hasCommit) {
			return;
		} else {
			rollBack();
		}
	}
}
